package spring.jpa.proxy

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.config.ConstructorArgumentValues
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar
import org.springframework.core.type.AnnotationMetadata
import org.springframework.core.type.filter.AssignableTypeFilter
import java.util.*

class JpaRepostoryRegistrar : ImportBeanDefinitionRegistrar {

    override
    fun registerBeanDefinitions(metadata: AnnotationMetadata?,
                                registry: BeanDefinitionRegistry) {

        val provider = getClassScanningProvider()

        provider.addIncludeFilter(AssignableTypeFilter(JpaRepository::class.java))

        provider.findCandidateComponents("spring.jpa.proxy")
                .forEach { definition ->
                    val beanName = getBeanName(definition)
                    val beanDefinition = genericBeanDefinition(beanName)
                    registry.registerBeanDefinition(beanName, beanDefinition)
                }
    }

    private fun genericBeanDefinition(beanName: String): GenericBeanDefinition {
        val beanDefinition = GenericBeanDefinition()
        beanDefinition.setFactoryBeanName("jpaRepositoryFactory")
        beanDefinition.setFactoryMethodName("createRepository")
        val argument = ConstructorArgumentValues()
        argument.addIndexedArgumentValue(0, beanName)
        beanDefinition.setConstructorArgumentValues(argument)
        return beanDefinition
    }

    private fun getClassScanningProvider(): ClassPathScanningCandidateComponentProvider {
        val provider: ClassPathScanningCandidateComponentProvider = object : ClassPathScanningCandidateComponentProvider(false) {
            protected override fun isCandidateComponent(beanDefinition: AnnotatedBeanDefinition): Boolean {
                val metadata: AnnotationMetadata = beanDefinition.getMetadata()
                return metadata.isIndependent() && metadata.isInterface() && JpaRepository::class.java.name != beanDefinition.getBeanClassName()
            }
        }
        return provider
    }

    private fun getBeanName(beanDefinition: BeanDefinition): String {
        return Optional.ofNullable(beanDefinition.getBeanClassName())
                .map({ beanClassName -> beanClassName.split(".") })
                .filter({ packages -> packages.size > 0 })
                .map({ packages -> packages[packages.size - 1] })
                .map({ className -> className.substring(0, 1).toLowerCase() + className.substring(1) })
                .orElseGet({ "" })
    }
}